#include <slothttpd/connection.hpp>
#include <slothttpd/logger.hpp>
#include <slothttpd/stage/receive_request.hpp>
#include <slothttpd/server.hpp>

#include <cassert>
#include <cstddef>

#include <stdexcept>

#include <boost/throw_exception.hpp>
#include <boost/bind.hpp>

namespace slothttpd {

unsigned long connection::current_connections = 0;
unsigned long connection::max_connections = 0;
unsigned long connection::total_requests = 0;

connection::connection_list_t connection::connection_list;

void connection::close_all() noexcept
{
    for (connection &c : connection_list) {
        c.close();
    }
}

connection::connection(boost::asio::io_service& ios) :
    pool(server::conf->pool_size),
    socket(ios),
    receive_buffers(server::conf->receive_buffers.size, server::conf->receive_buffers.nmax),
    requests_processed(0),
    ref_count_(0)
{
}

void connection::close() noexcept
{
    socket.close();
}

void connection::shutdown()
{
    try {
        socket.shutdown(boost::asio::socket_base::shutdown_both);
    } catch (const std::exception &ex) {
        //logger() << "can't shutdown socket: " << ex.what() << '\n';
    }
}

void connection::gracefull_shutdown()
{
    shutdown();
    close();
}

void connection::send_stock_response(const http::status::status_type &status) noexcept
{
    try {
        socket.shutdown(boost::asio::socket_base::shutdown_receive);
    } catch (const std::exception &ex) {
        // should not happen, if client shutdowns connection we should not send anything
        logger() << "can't shutdown receive before sending stock response: " << ex.what() << '\n';
    }
    
    boost::asio::async_write(socket, status.buffer_range(), boost::asio::transfer_all(),
                             boost::bind(&connection::handle_send_stock_response,
                                         connection_ptr(this),
                                         boost::asio::placeholders::error));
}

void connection::handle_send_stock_response(connection_ptr c, const boost::system::error_code &err)
{
    (void) err;
    
    c->gracefull_shutdown();
}

void connection::start()
{
    connection_list.push_front(*this);
    
    current_connections++;
    max_connections = std::max(current_connections, max_connections);
    
    try {
        boost::asio::ip::tcp::socket::non_blocking_io non_blocking_io(true);
        socket.io_control(non_blocking_io);
        
        connection::total_requests++;
        
        pool.construct<stage::receive_request>(this);
    } catch (const std::exception &ex) {
        logger() << ex.what() << std::endl;
    }
}

void intrusive_ptr_add_ref(connection *c) noexcept
{
    c->ref_count_++;
}

void intrusive_ptr_release(connection *c) noexcept
{
    c->ref_count_--;
    if (c->ref_count_ == 0) {
        c->requests_processed++;
        
        if (c->socket.is_open() && c->request.keep_alive) {
            try {
                connection::total_requests++;
                
                c->request.~request();
                new (&c->request) http::request();
                c->responce = http::response();
                
                c->receive_buffers.reset();
                
                // Note: pool should be the last thing to reset, because other fields can
                // still access objects in pool on destruction
                c->pool.reset();
                
                c->pool.construct<stage::receive_request>(c);
            } catch (const std::exception &ex) {
                logger() << ex.what() << std::endl;
                connection::current_connections--;
                delete c;
            }
        } else {
            connection::current_connections--;
            delete c;
        }
    }
}

} //namespace slothttpd
